﻿#include "gdalrasteriospeed.h"
#include <QDebug>
#include <QDropEvent>
#include <QElapsedTimer>
#include <QMimeData>

#include <gdal.h>
#include <gdal_priv.h>

#include <algorithm>
#include <functional>
#include <memory>

static std::string toU8String(const QString& s)
{
    QByteArray u8bs = s.toUtf8();
    return std::string(u8bs.data(), u8bs.size());
}

static QString fromU8String(const std::string& s)
{
    return QString::fromUtf8(s.data(), s.size());
}

GDALRasterIOSpeed::GDALRasterIOSpeed(QWidget* parent)
    : QTextEdit(parent)
{
    ui.setupUi(this);

    this->setWindowTitle(QStringLiteral("GDAL读图测试工具"));
    this->setAcceptDrops(true);
    this->setReadOnly(true);

    GDALAllRegister();
    GDALSetCacheMax64(1024 * 1024 * 40);   // 40MB
}

void GDALRasterIOSpeed::dragEnterEvent(QDragEnterEvent* event)
{
    const QMimeData* pMimeData = event->mimeData();
    if (pMimeData && pMimeData->hasUrls()) {
        // 接受文件拖放
        event->acceptProposedAction();
    }
}

// 在Qt 5.12.0或更高高版本中时，必须添加dragMoveEvent事件出来，否则拖拽操作会失效
void GDALRasterIOSpeed::dragMoveEvent(QDragMoveEvent* event)
{
    event->acceptProposedAction();
}

void GDALRasterIOSpeed::dropEvent(QDropEvent* event)
{
    const QMimeData* pMimeData = event->mimeData();
    if (pMimeData == nullptr || !pMimeData->hasUrls()) { return; }

    QList<QUrl> urls = pMimeData->urls();
    QString     path;

    qDebug() << urls;

    if (urls[0].isLocalFile()) { path = urls[0].toLocalFile(); }
    else if (urls[0].scheme() == QStringLiteral("http") ||
             urls[0].scheme() == QStringLiteral("https")) {
        path = QStringLiteral("/vsicurl/") + urls[0].toString();
    }
    else {
        return;
    }

    GDALRasterIOSpeedWorker* worker = new GDALRasterIOSpeedWorker;
    worker->moveToThread(&speedWorkerThread);

    connect(&speedWorkerThread, &QThread::finished, worker, &QObject::deleteLater);
    connect(this,
            &GDALRasterIOSpeed::startSpeedTest,
            worker,
            &GDALRasterIOSpeedWorker::doGDALRasterIOSpeedTest);
    connect(worker,
            &GDALRasterIOSpeedWorker::doGDALRasterIOSpeedTestMsg,
            this,
            &GDALRasterIOSpeed::updateSpeedTestMsg);
    connect(worker,
            &GDALRasterIOSpeedWorker::doGDALRasterIOSpeedTestBlockNumber,
            this,
            &GDALRasterIOSpeed::updateBlockCount);
    connect(worker,
            &GDALRasterIOSpeedWorker::doGDALRasterIOSpeedTestFinished,
            this,
            &GDALRasterIOSpeed::testFinished);

    this->clear();

    speedWorkerThread.start();
    emit startSpeedTest(path);

    this->setAcceptDrops(false);
}

void GDALRasterIOSpeed::updateSpeedTestMsg(QString msg)
{
    text += msg + QChar('\n');
    this->setText(text);
}

void GDALRasterIOSpeed::updateBlockCount(int count)
{
    QString title = QStringLiteral("已经读取块数 ") + QString::number(count);
    this->setWindowTitle(title);
}

void GDALRasterIOSpeed::testFinished()
{
    speedWorkerThread.quit();
    text.clear();
    this->setAcceptDrops(true);
    this->setWindowTitle(QStringLiteral("GDAL读图测试工具"));
}

GDALRasterIOSpeedWorker::GDALRasterIOSpeedWorker(QObject* parent)
    : QObject(parent)
{}

GDALRasterIOSpeedWorker::~GDALRasterIOSpeedWorker() {}

const int updateblock = 50;

void GDALRasterIOSpeedWorker::doGDALRasterIOSpeedTest(QString path)
{
    QString msg;
    int     allblockcount = 0;

    GDALDatasetH hDset = GDALOpen(toU8String(path).c_str(), GA_ReadOnly);
    if (hDset == NULL) {
        msg = QString::fromUtf8(CPLGetLastErrorMsg());
        emit doGDALRasterIOSpeedTestMsg(msg);
        emit doGDALRasterIOSpeedTestFinished();
        return;
    }
    std::unique_ptr<void, void (*)(GDALDatasetH)> dsetptr(hDset, GDALClose);

    msg = path + QStringLiteral("  影像打开成功");
    emit doGDALRasterIOSpeedTestMsg(msg);

    int nBands = GDALGetRasterCount(hDset);
    msg        = QStringLiteral("影像波段数为: ") + QString::number(nBands);
    emit doGDALRasterIOSpeedTestMsg(msg);

    if (nBands < 1) {
        msg = QStringLiteral("没有可用影像波段");
        emit doGDALRasterIOSpeedTestMsg(msg);
        emit doGDALRasterIOSpeedTestFinished();
        return;
    }
    if (nBands > 16) { nBands = 16; }

    GDALRasterBandH hBand = GDALGetRasterBand(hDset, 1);

    int width = GDALGetRasterBandXSize(hBand);
    int hight = GDALGetRasterBandYSize(hBand);
    int nXSzie, nYSize;
    GDALGetBlockSize(hBand, &nXSzie, &nYSize);

    GDALDataType datatype = GDALGetRasterDataType(hBand);

    msg = QStringLiteral("影像大小: ") + QString::number(width) + QString('X') +
          QString::number(hight) + QStringLiteral("      存储分块大小: ") +
          QString::number(nXSzie) + QString('X') + QString::number(nYSize) +
          QStringLiteral("      数据类型: ") + GDALGetDataTypeName(datatype);
    emit doGDALRasterIOSpeedTestMsg(msg);

    if (width < 4096 || hight < 4096) {
        msg = QStringLiteral("图像太小，不支持小图测试");
        emit doGDALRasterIOSpeedTestMsg(msg);
        emit doGDALRasterIOSpeedTestFinished();
        return;
    }
    if (width > 20480) { width = 20480; }
    if (hight > 20480) { hight = 20480; }

    msg = QStringLiteral("开始测速");
    emit doGDALRasterIOSpeedTestMsg(msg);

    std::vector<uint64_t> buffer((size_t)(1024 * 1024 * nBands), 0);


    std::function<void(QString, int, int&, int&, int)> func =
        [nBands, width, hight, datatype, buffer, this](
            QString Path, int blocksize, int& blockcount, int& allblockcount, int mode) {
            std::vector<std::pair<int, int>> xy0;

            // mode 0:行优先读取;  1:列优先;  2随机
            if (mode == 0) {
                // 读取起点的初始值设置在 16~31 之间的随机数
                for (int y = 16 + (rand() % 16); y < hight; y += blocksize) {
                    for (int x = 16 + (rand() % 16); x < width; x += blocksize) {
                        xy0.push_back(std::make_pair(x, y));
                    }
                }
            }
            else {
                for (int x = 16 + (rand() % 16); x < width; x += blocksize) {
                    for (int y = 16 + (rand() % 16); y < hight; y += blocksize) {
                        xy0.push_back(std::make_pair(x, y));
                    }
                }
            }
            if (mode == 2) { std::random_shuffle(xy0.begin(), xy0.end()); }

            for (const auto& xy : xy0) {
                int x  = xy.first;
                int y  = xy.second;
                int rw = (width - x) > blocksize ? blocksize : (width - x);
                int rh = (hight - y) > blocksize ? blocksize : (hight - y);

                GDALDatasetH hDset = GDALOpen(toU8String(Path).c_str(), GA_ReadOnly);
                if (hDset == NULL) { continue; }

                CPLErr err = GDALDatasetRasterIO(hDset, GF_Read, x, y, rw, rh,
                                                 (void*)buffer.data(), rw, rh, datatype,
                                                 nBands, NULL, 0, 0, 0);
                if (err == CPLE_None) { ++blockcount; }
                ++allblockcount;
                if (allblockcount % updateblock == 1) {
                    emit this->doGDALRasterIOSpeedTestBlockNumber(allblockcount);
                }
                GDALClose(hDset);
            }
        };


    auto runTestFunc = [this, path, func](
                           QString title, int mode, int blocksize, int& allblockcount) {
        int           blockcount = 0;
        QElapsedTimer et;
        et.start();
        func(path, blocksize, blockcount, allblockcount, mode);
        qint64 elapsed = et.elapsed();

        QString msg = title + QStringLiteral(" 读取总块数:") + QString::number(blockcount) +
                      QStringLiteral("  读取总耗时：") + QString::number(elapsed) +
                      QStringLiteral("毫秒\n") + QStringLiteral("平均每块耗时:") +
                      QString::number(double(elapsed) / blockcount) + QStringLiteral("毫秒\n");
        emit doGDALRasterIOSpeedTestMsg(msg);
    };

    runTestFunc(QStringLiteral("256分块(行优先)"), 0, 256, allblockcount);
    runTestFunc(QStringLiteral("256分块(列优先)"), 1, 256, allblockcount);
    runTestFunc(QStringLiteral("256分块(随机序)"), 2, 256, allblockcount);

    runTestFunc(QStringLiteral("512分块(行优先)"), 0, 512, allblockcount);
    runTestFunc(QStringLiteral("512分块(列优先)"), 1, 512, allblockcount);
    runTestFunc(QStringLiteral("512分块(随机序)"), 2, 512, allblockcount);

    runTestFunc(QStringLiteral("1024分块(行优先)"), 0, 1024, allblockcount);
    runTestFunc(QStringLiteral("1024分块(列优先)"), 1, 1024, allblockcount);
    runTestFunc(QStringLiteral("1024分块(随机序)"), 2, 1024, allblockcount);


    emit doGDALRasterIOSpeedTestFinished();
    return;
}
